Skip to content

fix: data races in tests#440

Merged
alnr merged 5 commits into
masterfrom
alnr/fix-race
Jun 9, 2026
Merged

fix: data races in tests#440
alnr merged 5 commits into
masterfrom
alnr/fix-race

Conversation

@alnr

@alnr alnr commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Calling cmdA.AddCommand(cmdB) modifies both cmdA and cmdB and hence is not goroutine safe.

Since we extensively test most commands and subcommands, we cannot have global var ... = &cobra.Command{...} declarations.

This diff is a lot more palatable when ignoring whitespace.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a Docker build target to produce a ready-to-use container image.
  • Tests

    • CI now runs Go tests with race detection enabled.
    • Added concurrent command-execution tests to improve stability.
  • Chores

    • Large internal refactor of CLI command initialization for improved maintainability.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@alnr, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 39 minutes and 3 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 027142e6-6eed-46f2-943e-ca967cd84d36

📥 Commits

Reviewing files that changed from the base of the PR and between 6ac119a and a2e4b00.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • cmd/cloudx/testhelpers/testhelpers.go
  • cmd/dev/ci/github/env.go
  • cmd/dev/newsletter/.snapshots/TestRenderMarkdownLong.json
  • go.mod
📝 Walkthrough

Walkthrough

This PR refactors all Cobra command definitions from package-level variables with init()-based registration to factory functions, eliminating global state races during concurrent command instantiation. Monorepo subcommands use scratch variables populated per-invocation to safely bind flags. Testing and CI now include race-condition detection.

Changes

Cobra command factory refactoring for concurrent safety

Layer / File(s) Summary
Root command factory foundation
cmd/version.go, cmd/dev.go, cmd/nodev.go, cmd/root.go, cmd/root_race_test.go
NewVersionCmd() and newDevCommands() factories replace package variables. Root command wires subcommands via factory calls. New concurrent test spawns goroutines creating/executing fresh root commands to detect races.
Monorepo subcommand with concurrency safeguards
cmd/dev/ci/monorepo/main.go, cmd/dev/ci/monorepo/run.go, cmd/dev/ci/monorepo/changes.go, cmd/dev/ci/monorepo/components.go
Package-level scratch variables are populated from per-command local flags at invocation time. NewCommand()/newXxxCmd() factories build fresh command instances so flags are bound safely per run.
Dev command hierarchy
cmd/dev/main.go, cmd/dev/headers/main.go, cmd/dev/headers/copyright.go, cmd/dev/headers/cp.go, cmd/dev/markdown/main.go, cmd/dev/markdown/render.go, cmd/dev/schema/main.go, cmd/dev/schema/render_version.go, cmd/dev/swagger/main.go, cmd/dev/swagger/sanitize.go
Each module now exposes a factory that constructs its root command and registers subcommands; command-local flags and handlers were moved into those factories.
CI utility commands
cmd/dev/ci/main.go, cmd/dev/ci/deps/main.go, cmd/dev/ci/deps/url.go, cmd/dev/ci/github/main.go, cmd/dev/ci/github/env.go, cmd/dev/ci/orbs/main.go, cmd/dev/ci/orbs/bump.go
CI root NewCommand() registers deps, github, and orbs subcommands. Deps/url, github/env, and orbs/bump command definitions are now produced by factory functions rather than package globals.
Release, openapi and newsletter chains
cmd/dev/openapi/main.go, cmd/dev/openapi/migrate.go, cmd/dev/release/main.go, cmd/dev/release/compile.go, cmd/dev/release/publish.go, cmd/dev/release/notify/main.go, cmd/dev/release/notify/draft.go, cmd/dev/release/notify/send.go, cmd/dev/newsletter/main.go, cmd/dev/newsletter/draft.go, cmd/dev/newsletter/send.go
Release, notify, publish, compile, openapi migrate, and newsletter commands moved to constructor-style factories; flags and execution logic are encapsulated in returned commands.
CI and build infrastructure
.github/workflows/ci.yaml, Makefile, go.mod
CI test job runs go test -race ./.... Makefile replaces post-release with a docker target that builds oryd/ory:latest-sqlite. go.mod removes an unused require.

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: data races in tests' accurately summarizes the main change: refactoring Cobra commands to use factory functions instead of global variables to prevent goroutine-unsafe command mutations during concurrent testing.
Description check ✅ Passed The pull request description clearly explains the problem (AddCommand modifies both commands making them unsafe for goroutines), the solution (replace global variables with factory functions), and provides context about testing. It adequately covers the motivation for these extensive changes without being unnecessarily verbose.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alnr/fix-race

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmd/dev/ci/monorepo/run.go (1)

94-96: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use mode parameter instead of package-level runMode in error message.

The error message references the package-level scratch variable runMode instead of the function parameter mode. This is inconsistent with the rest of the function and could produce misleading error messages in tests that call runWrapper directly.

Proposed fix
 	default:
-		return fmt.Errorf("unknown runMode: %s", runMode)
+		return fmt.Errorf("unknown runMode: %s", mode)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/dev/ci/monorepo/run.go` around lines 94 - 96, The default case in
runWrapper currently uses the package-level variable runMode in the error
string; update the error to reference the function parameter mode instead.
Locate the switch/default inside runWrapper and change the fmt.Errorf("unknown
runMode: %s", runMode) call to fmt.Errorf("unknown runMode: %s", mode) so the
error message reflects the actual argument passed to runWrapper rather than the
package-level scratch variable.
🧹 Nitpick comments (1)
cmd/dev/ci/monorepo/components.go (1)

17-19: ⚡ Quick win

Remove the componentMode package-level scratch variable and switch on localComponentMode directly.

componentMode is only used inside newComponentsCmd()’s Run closure in cmd/dev/ci/monorepo/components.go, so the package-level mutable state is unnecessary. Eliminating it avoids any residual shared-state risk while keeping the behavior the same.

♻️ Proposed change
-// Package-level scratch variable for the `components` subcommand, populated
-// from a per-command local in Run so concurrent construction does not race.
-var componentMode string
-
 func newComponentsCmd() *cobra.Command {
 	var localComponentMode string
 	c := &cobra.Command{
 		Use:   "components",
 		Short: "List components based on mode.",
 		Long:  `List components based on mode by reading dependency configs and displaying the dependency graph.`,
 		Run: func(cmd *cobra.Command, args []string) {
-			componentMode = localComponentMode
-
 			var graph ComponentGraph
 			_, _ = graph.getComponentGraph(rootDirectory)
 
-			switch componentMode {
+			switch localComponentMode {
 			case "affected":
 				affectedComponents := getAffectedComponents(&graph)
 				displayComponents(affectedComponents)
 			case "all":
 				allComponents := graph.components
 				displayComponents(allComponents)
 			case "changed":
 				changedComponents := getChangedComponents(&graph)
 				displayComponents(changedComponents)
 			case "involved":
 				involvedComponents := getInvolvedComponents(&graph)
 				displayComponents(involvedComponents)
 			default:
-				log.Fatalf("Unknown ListMode '%s'", componentMode)
+				log.Fatalf("Unknown ListMode '%s'", localComponentMode)
 			}
 		},
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/dev/ci/monorepo/components.go` around lines 17 - 19, Remove the
package-level variable componentMode and use the local variable
localComponentMode directly inside the Run closure of newComponentsCmd; update
any switch/case or conditional that references componentMode to instead
reference localComponentMode (inside newComponentsCmd and its Run), and delete
the now-unused componentMode declaration to eliminate unnecessary package-level
mutable state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/dev/ci/github/env.go`:
- Around line 54-59: The guard incorrectly treats an unset/empty
SWAGGER_SPEC_IGNORE_PKGS as non-empty because strings.Split("", ",") yields
[""], causing export of "-x " entries; update the logic around reading
SWAGGER_SPEC_IGNORE_PKGS (the os.Getenv call and ignorePkgs handling) to first
check if the env var is non-empty (or filter out empty strings after
strings.Split) before building the "-x " list and calling fmt.Printf, ensuring
ignorePkgs contains only non-empty package names before joining and printing.

In `@cmd/dev/ci/monorepo/main.go`:
- Line 18: Remove the unused scratch variable "branch" and its flag binding from
the monorepo command or wire it into downstream logic; specifically, delete the
"branch string" declaration and the corresponding "--branch" PersistentFlag
setup in main.go (the PersistentPreRun populates it) OR propagate the variable
into the existing flow (e.g., pass branch into the functions used by
run.go/changes.go or use it inside isPR()/change-detection logic) so the value
is consumed rather than unused.

---

Outside diff comments:
In `@cmd/dev/ci/monorepo/run.go`:
- Around line 94-96: The default case in runWrapper currently uses the
package-level variable runMode in the error string; update the error to
reference the function parameter mode instead. Locate the switch/default inside
runWrapper and change the fmt.Errorf("unknown runMode: %s", runMode) call to
fmt.Errorf("unknown runMode: %s", mode) so the error message reflects the actual
argument passed to runWrapper rather than the package-level scratch variable.

---

Nitpick comments:
In `@cmd/dev/ci/monorepo/components.go`:
- Around line 17-19: Remove the package-level variable componentMode and use the
local variable localComponentMode directly inside the Run closure of
newComponentsCmd; update any switch/case or conditional that references
componentMode to instead reference localComponentMode (inside newComponentsCmd
and its Run), and delete the now-unused componentMode declaration to eliminate
unnecessary package-level mutable state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a9f04a2a-9a62-468a-86d5-2ec0fa88be67

📥 Commits

Reviewing files that changed from the base of the PR and between 3ef403b and 88bcaba.

📒 Files selected for processing (39)
  • .github/workflows/ci.yaml
  • Makefile
  • cmd/dev.go
  • cmd/dev/ci/deps/main.go
  • cmd/dev/ci/deps/url.go
  • cmd/dev/ci/github/env.go
  • cmd/dev/ci/github/main.go
  • cmd/dev/ci/main.go
  • cmd/dev/ci/monorepo/changes.go
  • cmd/dev/ci/monorepo/components.go
  • cmd/dev/ci/monorepo/main.go
  • cmd/dev/ci/monorepo/run.go
  • cmd/dev/ci/orbs/bump.go
  • cmd/dev/ci/orbs/main.go
  • cmd/dev/headers/copyright.go
  • cmd/dev/headers/cp.go
  • cmd/dev/headers/main.go
  • cmd/dev/main.go
  • cmd/dev/markdown/main.go
  • cmd/dev/markdown/render.go
  • cmd/dev/newsletter/draft.go
  • cmd/dev/newsletter/main.go
  • cmd/dev/newsletter/send.go
  • cmd/dev/openapi/main.go
  • cmd/dev/openapi/migrate.go
  • cmd/dev/release/compile.go
  • cmd/dev/release/main.go
  • cmd/dev/release/notify/draft.go
  • cmd/dev/release/notify/main.go
  • cmd/dev/release/notify/send.go
  • cmd/dev/release/publish.go
  • cmd/dev/schema/main.go
  • cmd/dev/schema/render_version.go
  • cmd/dev/swagger/main.go
  • cmd/dev/swagger/sanitize.go
  • cmd/nodev.go
  • cmd/root.go
  • cmd/root_race_test.go
  • cmd/version.go
💤 Files with no reviewable changes (1)
  • Makefile

Comment thread cmd/dev/ci/github/env.go Outdated
Comment thread cmd/dev/ci/monorepo/main.go Outdated
alnr added 4 commits June 8, 2026 18:45
- golang.org/x/crypto v0.51.0 -> v0.53.0 (fixes multiple critical CVEs incl. GO-2026-5006, 5017, 5019, 5020, 5021, 5023, 5005)
- golang.org/x/net v0.54.0 -> v0.55.0 (fixes GO-2026-5026 critical)
- github.com/gomarkdown/markdown -> 20260417 (fixes GHSA-77fj-vx54-gvh7 high)

Regenerates the newsletter render snapshot to absorb a one-blank-line
cosmetic change in gomarkdown's HTML output.
Newer master commits of ory/x (and the ory/kratos / ory/hydra / ory/keto
that consume it) no longer pull github.com/docker/docker through the
x/sqlcon/dockertest chain, removing GO-2026-4883 (which has no fix
version available upstream).
Calling page.Close() while a BrowserContext.onRoute handler is still
in flight is a data race inside playwright-go. Call UnrouteAll with
UnrouteBehaviorWait first so registered handlers complete before the
page is closed.

Also fixes a pre-existing guard bug in `dev ci github env`: an empty
SWAGGER_SPEC_IGNORE_PKGS would print "-x " because strings.Split("", ",")
returns [""] rather than nil. Check the raw env var first.
@alnr alnr self-assigned this Jun 9, 2026
@alnr alnr merged commit e4dd7c4 into master Jun 9, 2026
17 checks passed
@alnr alnr deleted the alnr/fix-race branch June 9, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants